/*
 * Created by Jaren at 2021/8/17 17:08
 */

import 'dart:typed_data';
import 'package:inmotion_protocol/base/base_message.dart';
import 'package:inmotion_protocol/util/byte_util.dart';
import 'package:inmotion_protocol/util/int8_list_buffer.dart';
import 'package:inmotion_protocol/util/log.dart';

abstract class Decoder<T extends IMessage> {
  Int8ListBuffer buffer = Int8ListBuffer();

  List<T> decode(Uint8ClampedList? bytes, List<T> output) {
    LogUtils.d("start to decode");
    if (bytes == null || bytes.isEmpty) {
      LogUtils.d("Invalid bytes to decode.");
      // 重置
      return output;
    }

    // 先转义
    bytes = unescape(bytes);

    Uint8ClampedList? another;

    if (isStartWithPacketHead(bytes)) {
      // 有效包头
      buffer.clear();
      buffer.writeBytes(bytes);
    } else {
      // 非有效包头，可能为分包数据的剩余数据
      if (buffer.writerIndex > 0) {
        // 等待组包
        LogUtils.i("decode: Invalid head, data: " + ByteUtil.bytes2HexString(bytes));
        int headIndex = findPacketHead(bytes);
        if (headIndex < 0) {
          buffer.writeBytes(bytes);
        } else {
          // 新来的数据中存在包头
          // 写bytes中包头前的数据到sBuffer
          buffer.writeBytes(bytes, 0, headIndex);
          another = Uint8ClampedList(bytes.length - headIndex);
          // 剩余的拷贝到another
          ByteUtil.copy(bytes, headIndex, another, 0, another.length);
        }
      } else {
        // 在本次接收的数据中寻找是否存在包头
        int headIndex = findPacketHead(bytes);
        if (headIndex > 0) {
          // 存在包头
          // 写进buffer
          buffer.writeBytes(bytes, headIndex, bytes.length - headIndex);
        }
        // 不存在包头
        else {
          // 丢弃
          return output;
        }
      }
    }

    if (buffer.readableBytes() < minMsgLength()) {
      // buffer长度不足以组包
      return output;
    }

    int validPacketEndAt = findValidPacketEndIndex(buffer);

    if (validPacketEndAt < 0) {
      // 非完整包数据
      LogUtils.v("decode: Incomplete data, waiting for next pkg.");
      // another存在，继续解析another
      if (another != null) {
        return decode(another, output);
      }
      // 否则，等待下一包
      return output;
    } else if (validPacketEndAt > 0) {
      // 交由下一次decode处理
      LogUtils.d("decode: bytes length->" + bytes.length.toString() + ", validPacketEndAt->" + validPacketEndAt.toString() + ", bytes->" + ByteUtil.bytes2HexString(bytes));
      another = Uint8ClampedList(buffer.readableBytes() - validPacketEndAt);
      int oldReadIndex = buffer.readerIndex;
      buffer.readerIndex = validPacketEndAt;
      buffer.readBytes(another);
      buffer.readerIndex = oldReadIndex;
      // 将当前可读数据截止到一包完整数据后
      buffer.writerIndex = validPacketEndAt;
    } else {
      // =0, 有且仅有一包完整数据
      // do nothing
    }

    decrypt(buffer);

    Uint8ClampedList allBytes = Uint8ClampedList(buffer.readableBytes());
    buffer.readBytes(allBytes);
    buffer.clear();

    // 校验
    if (!check(allBytes)) {
      // 校验失败
      LogUtils.w("decode: ", Exception("Msg check failed! Data: \n" + ByteUtil.bytes2HexString(allBytes)));
      return output;
    }

    // 解析包数据
    T? msg = parse(allBytes);

    if (msg == null) {
      LogUtils.w("decode: ", Exception("Parse msg failed!\tData: " + ByteUtil.bytes2HexString(allBytes)));
    } else {
      LogUtils.i("decode: Parse msg success!\t" + msg.toString());
    }
    output.add(msg!);

    if (another != null) {
      decode(another, output);
    }
    return output;
  }

  /*
   * 是否是有效的一包的起始数据
   */

  bool isStartWithPacketHead(Uint8ClampedList bytes);

  int findPacketHead(Uint8ClampedList bytes);

  /*
   * 校验
   */
  bool check(Uint8ClampedList bytes);

  /*
   * 解密
   */
  void decrypt(Int8ListBuffer bytes);

  /*
   * decode
   */
  T? parse(Uint8ClampedList buf);

  /*
   * 反转义
   */
  Uint8ClampedList unescape(Uint8ClampedList bytes);

  int minMsgLength();

  int findValidPacketEndIndex(Int8ListBuffer buffer);
}

abstract class Encoder<T extends IMessage> {
  Int8ListBuffer buffer = Int8ListBuffer();

  Uint8ClampedList msgToBytes(T msg);

  Uint8ClampedList encrypt(Uint8ClampedList bytes);

  bool needEncrypt(T msg);

  /*
     * encode
     */
  Uint8ClampedList encode(dynamic msg) {
    Uint8ClampedList bytes = msgToBytes(msg);
    // 加密
    if (needEncrypt(msg)) {
      bytes = encrypt(bytes);
    }
    return bytes;
  }
}
